using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel.Channels;
using System.ServiceModel;
using System.Xml;
using System.IO;
using System.ServiceModel.Configuration;
using System.Configuration;
using System.Reflection;

namespace Microsoft.MSE
{
    public static class BindingHelper
    {
        public static Binding GetStandardBindingFromUri(Uri uri)
        {
            Binding bn = null;
            if (null == uri)
                throw new ArgumentNullException("uri");
            switch (uri.Scheme)
            {
                case "http":
                    bn = new WSHttpBinding();
                    ((WSHttpBinding)bn).Security.Mode = SecurityMode.None;
                    ((WSHttpBinding)bn).MaxReceivedMessageSize = 5000000;
                    break;
                case "net.tcp":
                    bn = new NetTcpBinding();
                    ((NetTcpBinding)bn).Security.Mode = SecurityMode.None;
                    ((NetTcpBinding)bn).MaxReceivedMessageSize = 5000000;
                    break;
                case "net.pipe":
                    bn = new NetNamedPipeBinding();
                    ((NetNamedPipeBinding)bn).Security.Mode = NetNamedPipeSecurityMode.None;
                    ((NetNamedPipeBinding)bn).MaxReceivedMessageSize = 5000000;
                    break;
            }
            return bn;
        }

        public static Binding DeserializeBinding(XmlElement BindingXml)
        {
            Binding newBinding = null;
            BindingsSection bnSec = null;

            if (null == BindingXml)
                throw new ArgumentNullException("Binding Xml");

            //make a copy since this function needs to modify the binding xml in order to deserialize it
            XmlElement bindingXmlCopy = BindingXml.Clone() as XmlElement;

            //BOTTO: get custom extensions, and remove their namespaces, 
            //add the extensions to wcf configuration and use it to load the BindingsSection
            Dictionary<string, string> extensionTypeNames = ExtractBindingExtensionsFromBindingConfig(bindingXmlCopy);
            if (extensionTypeNames != null && extensionTypeNames.Count > 0)
            {
                Configuration configSystem = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
                RegisterBindingExtensions(extensionTypeNames, configSystem);
                bnSec = BindingsSection.GetSection(configSystem);
            }
            else
            {
                bnSec = new BindingsSection();
            }


            bindingXmlCopy.RemoveAllAttributes();
            bindingXmlCopy.RemoveAttribute("xmlns", "");
            bindingXmlCopy.RemoveAttribute("xmlns", MseConstants.MSENamespace);

            if (string.IsNullOrEmpty(bindingXmlCopy.OuterXml))
                throw new ArgumentException("Binding Xml - OuterXml");

            string wcfBindingXml = string.Empty;
            try
            {
                wcfBindingXml = bindingXmlCopy.OuterXml;
                if (!string.IsNullOrEmpty(bindingXmlCopy.NamespaceURI))
                    wcfBindingXml = string.Format(@"<bindings>
                                                            <{0}>{1}</{0}>
                                                        </bindings>",
                                         bindingXmlCopy.FirstChild.Name,
                                         bindingXmlCopy.FirstChild.InnerXml);

                wcfBindingXml = wcfBindingXml.Replace("xmlns=\"" + MseConstants.MSENamespace + "\"", "");
                StringReader stm = new StringReader(wcfBindingXml);
                XmlReaderSettings xmlRdrSettings = new XmlReaderSettings();
                xmlRdrSettings.ConformanceLevel = ConformanceLevel.Fragment;
                XmlReader reader = XmlReader.Create(stm, xmlRdrSettings);

                //Deserialize Binding
                ConfigurationSection cfgSec = bnSec;
                Type cfgType = typeof(ConfigurationSection);
                MethodInfo deSer = cfgType.GetMethod("DeserializeSection",
                                        BindingFlags.Instance | BindingFlags.NonPublic);
                deSer.Invoke(cfgSec, new object[] { reader });
                List<BindingCollectionElement> bindingCollectionElements = bnSec.BindingCollections;

                BindingCollectionElement bindingCollectionElement = null;
                foreach (BindingCollectionElement bcelem in bnSec.BindingCollections)
                {
                    if (0 < bcelem.ConfiguredBindings.Count)
                    {
                        bindingCollectionElement = bcelem;
                        break;
                    }
                }

                if (null == bindingCollectionElement)
                    throw new ArgumentException("The deserialized section doesn't contain a configured binding");


                newBinding = GetBindingFromBindingCollectionElement(bindingCollectionElement, bnSec);

            }
            catch (Exception ex)
            {
                TraceHelper.TraceError("Error deserializing binding " + BindingXml.OuterXml +
                    " exception: " + ex.Message);
            }
            return newBinding;
        }
        private static Dictionary<string, string> ExtractBindingExtensionsFromBindingConfig(XmlElement bindingXml)
        {
            Dictionary<string, string> extensions = new Dictionary<string, string>();

            foreach (XmlAttribute attrib in bindingXml.Attributes)
            {
                string nsName = attrib.Name.Substring(attrib.Name.LastIndexOf(':') + 1);
                if (attrib.Value.StartsWith("clr-namespace:"))
                {
                    string[] ns = attrib.Value.Split(new char[] { ':', ';', '=' });
                    XmlNodeList nodes = bindingXml.GetElementsByTagName("*", attrib.Value);
                    while (nodes.Count > 0)
                    {
                        XmlNode node = nodes[0];
                        //create the assembly qualified name for the extension type
                        extensions.Add(node.LocalName, string.Format("{0}.{1},{2}", ns[1], node.LocalName, ns[3]));

                        //replace element with one w/o clr namespace
                        XmlNode newNode = bindingXml.OwnerDocument.CreateElement(node.LocalName);
                        node.ParentNode.ReplaceChild(newNode, node);
                        newNode.InnerXml = node.InnerXml;
                        foreach (XmlAttribute att in node.Attributes)
                        {
                            newNode.Attributes.Append(att);
                        }
                    }
                }
            }

            return extensions;
        }
        private static void RegisterBindingExtensions(Dictionary<string, string> extensionList, Configuration configSystem)
        {
            if (extensionList != null && extensionList.Count > 0)
            {
                ServiceModelSectionGroup configSection = ServiceModelSectionGroup.GetSectionGroup(configSystem);

                //if we don't use the supplied configSystem, this is an alternative way to prepare the extensions collection for accepting new items.
                //ExtensionsSection extensions = ConfigurationManager.GetSection("system.serviceModel/extensions") as ExtensionsSection;
                //Type ceCollectionType = typeof(ConfigurationElementCollection);
                //FieldInfo fieldInfo = ceCollectionType.GetField("bReadOnly",
                //                        BindingFlags.Instance | BindingFlags.NonPublic);
                //fieldInfo.SetValue(extensions.BindingElementExtensions, false);
                //fieldInfo.SetValue(extensions.BindingExtensions, false);

                foreach (string key in extensionList.Keys)
                {
                    Type t = Type.GetType(extensionList[key]);
                    if (t == null)
                    {
                        throw new ArgumentException(string.Format("The binding contains an unrecognized extension {0}.", key));
                    }
                    if (t.IsSubclassOf(typeof(BindingElementExtensionElement)))
                    {
                        if (!configSection.Extensions.BindingElementExtensions.ContainsKey(key))
                            configSection.Extensions.BindingElementExtensions.Add(new ExtensionElement(key, extensionList[key]));
                    }
                    else if (t.IsSubclassOf(typeof(BindingCollectionElement)))
                    {
                        if (!configSection.Extensions.BindingExtensions.ContainsKey(key))
                            configSection.Extensions.BindingExtensions.Add(new ExtensionElement(key, extensionList[key]));
                    }
                    else
                    {
                        throw new ArgumentException(string.Format("The binding contains an unrecognized extension {0}.", extensionList[key]));
                    }
                }
            }
        }

        private static Binding GetBindingFromBindingCollectionElement(
                BindingCollectionElement bindingCollectionElement,
                BindingsSection bSec)
        {
            Binding newBinding = null;
            //BindingElementCollection elements = null;
            bool isCustomBinding = false;
            try
            {
                if (null != bindingCollectionElement)
                {
                    if (bindingCollectionElement.BindingType == typeof(BasicHttpBinding))
                        newBinding = new BasicHttpBinding();
                    else if (bindingCollectionElement.BindingType == typeof(CustomBinding))
                    {
                        newBinding = new CustomBinding();
                        isCustomBinding = true;
                    }
                    else if (bindingCollectionElement.BindingType == typeof(NetMsmqBinding))
                        newBinding = new NetMsmqBinding();
                    else if (bindingCollectionElement.BindingType == typeof(NetNamedPipeBinding))
                        newBinding = new NetNamedPipeBinding();
                    else if (bindingCollectionElement.BindingType == typeof(NetPeerTcpBinding))
                        newBinding = new NetPeerTcpBinding();
                    else if (bindingCollectionElement.BindingType == typeof(WSDualHttpBinding))
                        newBinding = new WSDualHttpBinding();
                    else if (bindingCollectionElement.BindingType == typeof(WSHttpBinding))
                        newBinding = new WSHttpBinding();
                    else if (bindingCollectionElement.BindingType == typeof(NetTcpBinding))
                        newBinding = new NetTcpBinding();

                    //Should we delete all default bindings?
                    //ApplyConfiguration might alreadey do just that
                    //elements = newBinding.CreateBindingElements();
                    if (isCustomBinding)
                    {
                        CustomBindingCollectionElement cbce = bSec.CustomBinding;

                        Type bExeType = typeof(BindingElementExtensionElement);
                        MethodInfo bExeMi = bExeType.GetMethod("CreateBindingElement",
                                                BindingFlags.Instance | BindingFlags.NonPublic);

                        foreach (CustomBindingElement custBe in cbce.Bindings)
                        {
                            string strBe = custBe.Name;
                            foreach (BindingElementExtensionElement bExe in custBe)
                            {

                                ((CustomBinding)newBinding).Elements.Add(
                                            (BindingElement)bExeMi.Invoke(bExe, null));
                            }
                        }
                    }
                    else
                    {
                        foreach (IBindingConfigurationElement bEleEx in bindingCollectionElement.ConfiguredBindings)
                        {
                            bEleEx.ApplyConfiguration(newBinding);
                        }
                    }
                }

            }
            catch (Exception ex)
            {
                TraceHelper.TraceError("Error in GetBindingFromBindingCollectionElement. Exception: " + ex.Message);
            }
            return newBinding;
        }
    }
}

/*
 * 
                 CustomBindingCollectionElement cbce = bnSec.CustomBinding;

                Type bExeType = typeof(BindingElementExtensionElement);
                MethodInfo bExeMi = bExeType.GetMethod("CreateBindingElement",
                                        BindingFlags.Instance | BindingFlags.NonPublic);

                foreach (CustomBindingElement custBe in cbce.Bindings)
                {
                    string strBe = custBe.Name;
                    foreach (BindingElementExtensionElement bExe in custBe)
                    {

                        ((CustomBinding)newBinding).Elements.Add(
                                    (BindingElement)bExeMi.Invoke(bExe, null));
                    }
                }

* 
*/
